extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
// #include <libavfilter/avfilter.h>
// #include <libavfilter/buffersrc.h>
// #include <libavfilter/buffersink.h>
#include <libavutil/avutil.h>
#include <libavutil/pixfmt.h>
// #include <libswresample/swresample.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>

#include <libavutil/buffer.h>
#include <libavutil/error.h>
#include <libavutil/mem.h>
}

#include <thread>
#include <mutex>
#include <list>
#include <vector>

typedef struct tagStDecode
{
	AVPixelFormat format;
	SwsContext *sws = nullptr;
	AVPacket *pkt = nullptr;
	AVCodecContext *cd = nullptr;
	AVCodecParserContext *parser = nullptr;
	const AVCodec *codecDecode = nullptr;
	AVFrame *frame = nullptr;
	void (*recv)(void *obj, unsigned char *ydata, int ylen, 
		unsigned char *udata, int ulen, unsigned char *vdata, int vlen, 
		int width, int height, int frame_index);
	void *user_obj = nullptr;
} _StDecode;

int n_hw_accels;
AVPixelFormat g_format;
void SetHWAccels(int type) { n_hw_accels = type; }
void SetBitmapFormat(int format) { g_format = (AVPixelFormat)format; }

bool Decode(void *handle, AVPacket *pkt)
{
	_StDecode *StDecode = (_StDecode *)handle;
	if (!StDecode->cd)
		return false;
	int ret = avcodec_send_packet(StDecode->cd, pkt);
	if (ret < 0)
		return false;
	while (ret >= 0)
	{
		ret = avcodec_receive_frame(StDecode->cd, StDecode->frame);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			break;
		else if (ret < 0)
			return false;

		if (StDecode->frame != nullptr && StDecode->frame->linesize[0] > 0)
		{
			StDecode->recv(StDecode->user_obj, StDecode->frame->data[0], StDecode->frame->linesize[0],
				StDecode->frame->data[1], StDecode->frame->linesize[1],
				StDecode->frame->data[2], StDecode->frame->linesize[2],
				StDecode->frame->width, StDecode->frame->height, StDecode->cd->frame_number);

			if (StDecode->frame)
				av_frame_unref(StDecode->frame);
		}
	}
	return true;
}
extern "C" void* BeginDecode(void *obj, int code, void (*decode)(void *obj, 
	unsigned char *ydata, int ylen, unsigned char *udata, int ulen, 
	unsigned char *vdata, int vlen, int width, int height, int frame_index))
{
	_StDecode *StDecode = new _StDecode();
	StDecode->user_obj = obj;
	StDecode->recv = decode;
	StDecode->format = g_format;

	av_log_set_level(AV_LOG_QUIET);
	StDecode->codecDecode = (code == AV_CODEC_ID_H264 ? 
		avcodec_find_decoder(AV_CODEC_ID_H264) : (code == AV_CODEC_ID_H265 ? 
		avcodec_find_decoder(AV_CODEC_ID_H265) : avcodec_find_decoder((AVCodecID)code)));
	if (!StDecode->codecDecode)
		return nullptr;
	StDecode->cd = avcodec_alloc_context3(StDecode->codecDecode);
	if (!StDecode->cd)
		return nullptr;
	if (avcodec_open2(StDecode->cd, StDecode->codecDecode, NULL) < 0)
		return nullptr;
	StDecode->parser = av_parser_init(StDecode->codecDecode->id);
	if (!StDecode->parser){
		return nullptr;
	}
	StDecode->frame = av_frame_alloc();
	StDecode->pkt = av_packet_alloc();
	return StDecode;
}
extern "C" void ImageDecode(void *handle, unsigned char *data, int len)
{
	_StDecode *StDecode = (_StDecode *)handle;
	if (!StDecode->pkt || !StDecode->parser || !StDecode->cd)
		return;
	while (len > 0)
	{
		int ret = av_parser_parse2(StDecode->parser, StDecode->cd, &StDecode->pkt->data,
			&StDecode->pkt->size, data, len, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
		if (ret < 0)
			break;
		data += ret;
		len -= ret;
		if (StDecode->pkt->size > 0 && !Decode(handle, StDecode->pkt))
			break;
		if (StDecode->pkt)
			av_packet_unref(StDecode->pkt);
	}
}
extern "C" void EndDecode(void *handle)
{
	Decode(handle, nullptr);
	_StDecode *StDecode = (_StDecode *)handle;
	if (StDecode->frame)
		av_frame_free(&StDecode->frame);
	if (StDecode->pkt)
		av_packet_unref(StDecode->pkt);
	if (StDecode->parser)
		av_parser_close(StDecode->parser);
	if (StDecode->cd)
		avcodec_close(StDecode->cd);
	if (StDecode->sws)
		sws_freeContext(StDecode->sws);
	if (StDecode)
	{
		delete StDecode;
		StDecode = nullptr;
	}
}